package com.swak.boot.config.jdbc;

import static com.swak.boot.Application.APP_LOGGER;

import java.lang.reflect.Field;
import java.util.List;

import javax.sql.DataSource;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import com.swak.Constants;
import com.swak.boot.config.jdbc.database.ConfigurationCustomizer;
import com.swak.boot.config.jdbc.database.DruidDataSourceAutoConfiguration;
import com.swak.boot.config.jdbc.database.HikariDataSourceAutoConfiguration;
import com.swak.boot.config.jdbc.database.SpringBootVFS;
import com.swak.boot.config.jdbc.database.SqlLiteDataSourceAutoConfiguration;
import com.swak.boot.config.jdbc.sharding.ShardingJdbcConfiguration;
import com.swak.persistence.QueryCondition;
import com.swak.persistence.mybatis.UnifyInterceptor;
import com.swak.persistence.mybatis.enhance.EnhanceMapperRegistry;
import com.swak.persistence.mybatis.filter.Filter;
import com.swak.persistence.mybatis.filter.MsingFilter;
import com.swak.persistence.mybatis.filter.PagingFilter;
import com.swak.persistence.mybatis.filter.ShardingFilter;
import com.swak.utils.Lists;

/**
 * Mybatis的自动配置
 * 
 * @author lifeng
 * @date 2020年4月15日 下午9:05:30
 */
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, ShardingJdbcConfiguration.class,
		SqlLiteDataSourceAutoConfiguration.class, DruidDataSourceAutoConfiguration.class,
		HikariDataSourceAutoConfiguration.class })
@ConditionalOnProperty(prefix = Constants.APPLICATION_PREFIX, name = "enableMybatis", matchIfMissing = true)
public class MybatisAutoConfiguration {
	private final MybatisProperties properties;
	private final Interceptor[] interceptors;
	private final List<ConfigurationCustomizer> configurationCustomizers;

	public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
			ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
		APP_LOGGER.debug("Loading Mybatis");
		this.properties = properties;
		this.interceptors = interceptorsProvider.getIfAvailable();
		this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
	}

	@Bean
	@ConditionalOnMissingBean
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
		SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
		factory.setDataSource(dataSource);
		factory.setVfs(SpringBootVFS.class);

		Configuration configuration = new Configuration();
		configuration.setLazyLoadingEnabled(this.properties.isLazyLoadingEnabled());
		configuration.setAggressiveLazyLoading(this.properties.isAggressiveLazyLoading());

		this.customizeConfiguration(configuration);
		this.enhanceMapperRegistry(configuration);
		this.defaultConfiguration(configuration);
		factory.setConfiguration(configuration);

		if (this.properties.getConfigurationProperties() != null) {
			factory.setConfigurationProperties(this.properties.getConfigurationProperties());
		}
		if (!ObjectUtils.isEmpty(this.interceptors)) {
			factory.setPlugins(this.interceptors);
		}
		if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
			factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
		}
		if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
			factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
		}
		factory.setMapperLocations(this.properties.resolveMapperXmlLocations());
		return factory.getObject();
	}

	private void customizeConfiguration(Configuration configuration) {
		if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
			for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
				customizer.customize(configuration);
			}
		}
	}

	private void enhanceMapperRegistry(Configuration configuration) {
		try {
			EnhanceMapperRegistry mapperRegistry = new EnhanceMapperRegistry(configuration);
			Field field = Configuration.class.getDeclaredField("mapperRegistry");
			field.setAccessible(true);
			field.set(configuration, mapperRegistry);
		} catch (Throwable e) {
		}
	}

	private void defaultConfiguration(Configuration configuration) {

		// 组装过滤器
		List<Filter> filters = Lists.newArrayList();
		if (properties.isOpenMasterSlave()) {
			filters.add(new MsingFilter());
		}
		if (properties.isOpenSharding()) {
			filters.add(new ShardingFilter());
		}
		filters.add(new PagingFilter());
		//filters.add(new IllegalSQLCheckFilter());

		// 默认的拦截器
		UnifyInterceptor interceptor = new UnifyInterceptor(filters);
		configuration.addInterceptor(interceptor);

		// 默认的别名
		configuration.getTypeAliasRegistry().registerAlias("queryCondition", QueryCondition.class);
	}

	/**
	 * SqlSessionTemplate - 会对 SqlSession 做代理处理；
	 * 
	 * @param sqlSessionFactory
	 * @return
	 */
	@Bean
	@ConditionalOnMissingBean
	public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		ExecutorType executorType = this.properties.getExecutorType();
		if (executorType != null) {
			return new SqlSessionTemplate(sqlSessionFactory, executorType);
		} else {
			return new SqlSessionTemplate(sqlSessionFactory);
		}
	}
}
